const { createApp } = Vue;
let app = createApp({
    beforeMount() {
        this.listenCopyCodes();
        this.initMarkdown();
        this.listenImageClick();
    },
    data() {
        return {
            isMobile: false,
            roleList: [],
            roleId: 1,
            loginUser: {
                avatar: window.userAvatar || "/app/ai/avatar/user.png"
            },
            keyword: '',
            showAddressBook: true,
            box: {
                showContextMenu: false,
                showParams: false,
                showApiKey: false,
                showSendMethod: false,
                showRoleInfo: false,
                showSystemInfo: false,
                showMore: false,
            },
            showBuyLink: false,
            apiKey: '',
            defaultModels: {
                "gpt-3.5-turbo": "gpt-3.5-turbo",
                "gpt-3.5-turbo-0613": "gpt-3.5-turbo-0613",
                "gpt-3.5-turbo-16k": "gpt-3.5-turbo-16k",
                "gpt-4": "gpt-4",
                "gpt-4-32k": "gpt-4-32k",
                "dall.e": "DALL.E作图",
            },
            defaultParams: {
                model: 'gpt-3.5-turbo-0613',
                maxTokens: 2000,
                temperature: 0.5,
                contextNum: 12,
            },
            roleInfo: {
                roleId: 0,
                name: '',
                desc: '',
                avatar: '',
                pinned: 0,
                lastTime: 0,
                greeting: '',
                rolePrompt: '',
            },
            contextMenu: {
                top: 0,
                left: 0,
                roleId: 0,
            },
            sendMethod: 'Enter', // Ctrl-Enter
            hoverMessageId: 0,
            isSlidedIn: false,
            isSlidedOut: false,
        }
    },
    mounted() {
        window.addEventListener('resize', this.checkMobile);
        this.checkMobile();
        this.loadAll();
        this.showBuyLink = location.host === 'www.workerman.net';
    },
    watch: {
        'chat.params': {
            deep: true,
            handler(a, b) {
                if (b === undefined) return;
                this.saveRoles();
            }
        }
    },
    computed: {
        chat() {
            for(const item of this.roleList) {
                if (this.roleId === item.roleId) {
                    return item;
                }
            }
            return {};
        },
        filter() {
            return [...this.roleList.filter((item) => {
                return !item.deleted&&item.name.indexOf(this.keyword) !== -1;
            })].sort((a, b)=>{
                if (a.pinned !== b.pinned) {
                    return b.pinned - a.pinned;
                }
                return b.lastTime - a.lastTime;
            });
        },
        showShadowLayer() {
            return Object.values(this.box).some(value => value);
        }
    },
    methods: {
        loadAll() {
            this.sendMethod = localStorage.getItem('ai.send-method') || 'Enter';
            this.apiKey = localStorage.getItem('ai.api-key') || '';
            this.roleList = JSON.parse(localStorage.getItem('ai.role-list') || '[]');
            this.formatRoles();
            this.loadRoles();
            this.loadRoleId();
        },
        loadRoles(reset, cb) {
            $.ajax({
                url: '/app/ai/roles.json?v=' + new Date().getTime(),
                success:  (roles) => {
                    if (reset) this.roleList = [];
                    this.roleList = this.roleList.concat(roles.filter(role => !this.roleList.some(chat => chat.roleId === role.roleId)));
                    this.formatRoles();
                    this.loadRoleId();
                    cb && cb();
                }
            });
        },
        loadRoleId() {
            this.roleId = parseInt(localStorage.getItem('ai.roleId')) || this.filter[0]?.roleId || 0;
            this.scrollToBottom(true, false);
        },
        saveRoleId() {
            localStorage.setItem('ai.roleId', this.roleId);
        },
        switchRoleId(roleId) {
            this.roleId = roleId;
            this.saveRoleId();
            this.scrollToBottom(true, false);
            if (this.isMobile) {
                this.showAddressBook = false;
                this.isSlidedOut = false;
                this.isSlidedIn = true;
            }
        },
        saveSendMethod() {
            localStorage.setItem('ai.send-method', this.sendMethod);
        },
        saveApiKey() {
            localStorage.setItem('ai.api-key', this.apiKey);
        },
        sendMessage() {
            const chat = this.chat;
            if (chat.content === '' || chat.loading) {
                return;
            }
            let context = this.getContext();
            chat.messages.push({
                "id": this.genId(),
                "role": "user",
                "created": new Date().getTime(),
                "completed" : true,
                "choices": [
                    {
                        "delta": {
                            "content": chat.content
                        }
                    }
                ]
            });
            let lastId = this.genId();
            const lastMessage = {
                "id": lastId,
                "role": "assistant",
                "created": new Date().getTime(),
                "completed" : false,
                "choices": [
                    {
                        "delta": {
                            "content": this.chat.params.model === 'dall.e' ? '生成中...' : ''
                        }
                    }
                ]
            };
            chat.messages.push(lastMessage);
            // 每个对话只保留最近40条数据
            chat.messages = chat.messages.slice(-40);
            chat.lastTime = new Date().getTime();
            this.saveRoles();
            chat.lastChunkIndex = 0;
            chat.loading = true;
            this.scrollToBottom(true);
            $.ajax({
                url: '/app/ai/message/send',
                data: {
                    rolePrompt: this.chat.rolePrompt,
                    prompt: chat.content,
                    context: context,
                    model: chat.params.model,
                    apiKey: this.apiKey,
                    maxTokens: chat.params.maxTokens,
                    temperature: chat.params.temperature
                },
                type: 'POST',
                dataType: 'json',
                complete: () => {
                    let message = app.lastMessage(chat);
                    if (!chat.loading || message.id !== lastId) {
                        return;
                    }
                    chat.lastChunkIndex = 0;
                    chat.loading = false;
                    lastMessage.completed = true;
                    app.saveRoles();
                },
                success: function (res) {
                    let message = app.lastMessage(chat);
                    if (!chat.loading || message.id !== lastId) {
                        return;
                    }
                    if (res && res.error && res.error.message) {
                        app.lastMessage(chat).choices[0].delta.content = res.error.message;
                    } else if (res && res.data && res.data[0] && res.data[0].url) {
                        app.lastMessage(chat).choices[0].delta.content = `![](${res.data[0].url})`;
                    } else {
                        console.log(res);
                    }
                },
                xhr: this.xhrOnProgress(function(event) {
                    let message = app.lastMessage(chat);
                    // 已经取消
                    if (!chat.loading || message.id !== lastId) {
                        return;
                    }
                    const xhr = event.target
                    const {responseText} = xhr
                    let thunks = responseText.substring(chat.lastChunkIndex);
                    chat.lastChunkIndex = responseText.length;
                    const arr = thunks.split("\n");
                    arr.forEach(function (chunk) {
                        if (chunk === '') return;
                        chunk = chunk.substring(6).trim();
                        if (chunk === '' || chunk === '[DONE]') return;
                        try {
                            const data = JSON.parse(chunk);
                            if (message.error) {
                                message.choices[0].delta.content += message.error.message || '';
                            } else {
                                message.choices[0].delta.content += data.choices[0].delta.content || '';
                            }
                            app.scrollToBottom();
                        } catch (e) {}
                    });
                })
            });
            chat.content = '';
        },
        deleteMessage(id) {
            this.chat.messages = this.chat.messages.filter(message => message.id !== id);
            this.saveRoles();
        },
        openContextMenu(roleId, event) {
            this.contextMenu.roleId = roleId;
            const windowHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
            const contextMenuHeight = 110;
            const contextMenuTop = event.clientY > windowHeight - contextMenuHeight ? event.clientY - contextMenuHeight : event.clientY;
            this.contextMenu.top = contextMenuTop;
            this.contextMenu.left = event.clientX + 5;
            this.box.showContextMenu = true;
        },
        closeContextMenu() {
            this.contextMenu.roleId = 0;
            this.box.showContextMenu = false;
        },
        formatRoles() {
            for (const chat of this.roleList) {
                if (!chat.messages) {
                    chat.messages = [];
                }
                if (!chat.messages.length && chat.greeting) {
                    chat.messages = [{
                        "role": "assistant",
                        "created": new Date().getTime(),
                        "choices": [
                            {
                                "delta": {
                                    "content": chat.greeting || ''
                                }
                            }
                        ]
                    }];
                }
                chat.content = chat.content || '';
                chat.loading = false;
                chat.lastChunkIndex = 0;
                chat.lastTime = chat.lastTime || 0;
                chat.pinned = chat.pinned || 0;
                chat.params = {...this.defaultParams, ...chat.params};
                for (const message of chat.messages) {
                    message.completed = true;
                }
            }
        },
        sendMethodSelect(item){
            this.box.showSendMethod = false;
            this.sendMethod = item;
            this.saveSendMethod(item);
        },
        showPanel(name) {
            this.hideAll();
            this.box['show' + name] = true;
        },
        hideAll() {
            for (let key in this.box) {
                this.box[key] = false;
            }
            this.clearRoleInfo();
            this.closeContextMenu();
        },
        showRoleInfoBox() {
            this.clearRoleInfo();
            this.box.showRoleInfo = true;
        },
        clearRoleInfo() {
            this.roleInfo.roleId = 0;
            this.roleInfo.avatar = '/app/ai/avatar/ai.png';
            this.roleInfo.name = this.roleInfo.desc = this.roleInfo.greeting = this.roleInfo.rolePrompt = '';
        },
        addRole() {
            this.clearRoleInfo();
            this.box.showRoleInfo = true;
        },
        editRole(roleId) {
            roleId = roleId || this.contextMenu.roleId;
            this.clearRoleInfo();
            this.roleInfo.roleId = roleId;
            for (const item of this.roleList) {
                if (item.roleId == roleId) {
                    this.roleInfo = {... item};
                    break;
                }
            }
            this.closeContextMenu();
            this.box.showRoleInfo = true;
        },
        deleteRole() {
            const roleId = this.contextMenu.roleId;
            for (const item of this.roleList) {
                if (item.roleId == roleId) {
                    item.deleted = true;
                    break;
                }
            }
            this.saveRoles();
            this.closeContextMenu();
        },
        pinRole() {
            const roleId = this.contextMenu.roleId;
            for (const item of this.roleList) {
                if (item.roleId == roleId) {
                    item.pinned = item.pinned ? 0 : 1;
                    break;
                }
            }
            this.saveRoles();
            this.closeContextMenu();
        },
        saveRole() {
            const time = new Date().getTime();
            this.roleInfo.roleId = this.roleInfo.roleId || time;
            this.roleInfo.pinned = 0;
            const index = this.roleList.findIndex(item => item.roleId === this.roleInfo.roleId);
            if (index !== -1) {
                this.roleList[index] = {
                    ...this.roleList[index],
                    ...this.roleInfo
                };
                this.hideAll();
                this.saveRoles();
                return;
            }
            const roleInfo = {... this.roleInfo};
            roleInfo.lastTime = new Date().getTime();
            this.roleList.push(roleInfo);
            this.formatRoles();
            this.saveRoles();
            this.hideAll();
        },
        saveRoles() {
            localStorage.setItem('ai.role-list', JSON.stringify(this.roleList));
        },
        uploadAvatar() {
            const that = this;
            const formdata = new FormData();
            formdata.append('avatar', $('#avatar')[0].files[0]);
            $.ajax({
                url:'/app/ai/upload/avatar',
                type:'post',
                contentType:false,
                processData:false,
                data:formdata,
                success:function (res) {
                    that.roleInfo.avatar = res.data.url;
                }
            });
        },
        confetti() {
            function randomInRange(min, max) {
                return Math.random() * (max - min) + min;
            }
            for (let i = 0; i < 8; i++) {
                confetti({
                    startVelocity: 30,
                    particleCount: 4,
                    spread: 360,
                    ticks: 60,
                    origin: { x: randomInRange(0.15, 0.9), y: randomInRange(0, 0.6) }
                });
            }
        },
        cancel(){
            this.chat.loading = false;
            let message = this.lastMessage(this.chat);
            message.completed = true;
        },
        destroy(){
            this.cancel();
            this.chat.messages = [];
            this.formatRoles();
            this.confetti();
            this.saveRoles();
        },
        lastMessage(chat) {
            return chat.messages[chat.messages.length - 1];
        },
        resetSystem() {
            localStorage.clear();
            this.loadRoles(true, ()=>{this.confetti();});
            this.hideAll();
        },
        getContext() {
            let context = [];
            let contextNum = this.chat.params.contextNum;
            if (contextNum === 0) {
                this.chat.messages = [];
            } else {
                this.chat.messages.slice(-contextNum).forEach(function (message) {
                    context.push({role: message.role, content: message.choices[0].delta.content || ''});
                });
            }
            return context;
        },
        listenCopyCodes() {
            $(document).on('click', '.hljs .block-copy', function() {
                app.copyToclipboard($(this).parent().parent().next().text())
            });
        },
        copyToclipboard(content) {
            if (navigator.clipboard) return navigator.clipboard.writeText(content);
            let input = document.createElement('textarea');
            input.setAttribute('readonly', 'readonly')
            input.value = content;
            document.body.appendChild(input)
            input.select()
            if (document.execCommand('copy'))
                document.execCommand('copy')
            document.body.removeChild(input)
        },
        listenImageClick() {
            $(document).on('click', '.message-list .markdown-body img', function() {
                let imgUrl = $(this).attr('src');
                const imgExt = imgUrl.split('.').pop();
                if (imgExt.length <= 4) {
                    imgUrl = imgUrl.replace('-sm.', '-lg.');
                }
                $('.overlay img').attr('src', imgUrl);
                $('.overlay').show();
            });
            $(document).on('click', '.overlay, .close', function() {
                $('.overlay').hide();
            });
        },
        initMarkdown() {
            this.md =  window.markdownit().set({
                linkify: true,
                breaks: true,
                html: false,
                highlight: function (str, lang) {
                    const header = `<div class="position-relative"><div class="position-absolute d-flex justify-content-end align-items-center" style="right:-15px;top:-15px;"><span class="text-secondary">${lang}</span><span class="block-copy ml-2 iconfont iconfont-bg"></span></div></div>`;
                    if (lang && hljs.getLanguage(lang)) {
                        return '<pre class="hljs">' + header + '<code>' + hljs.highlight(str, { language: lang }).value + '</code></pre>';
                    }
                    return '<pre class="hljs">' + header + '<code>' + hljs.highlightAuto(str).value + '</code></pre>';
                }
            });
        },
        markdown(content) {
            return this.md.render(content);
        },
        xhrOnProgress(fun) {
            return function() {
                let xhr = $.ajaxSettings.xhr();
                if (typeof fun !== 'function')
                    return xhr;
                if (fun) {
                    xhr.addEventListener('progress', fun);
                }
                return xhr;
            }
        },
        handleEnter(event) {
            if ((event.key === this.sendMethod && !event.ctrlKey) || (event.key !== this.sendMethod && event.ctrlKey)) {
                event.preventDefault();
                this.sendMessage();
            }
        },
        scrollToBottom(force, smooth) {
            const messageBox = this.$refs.messageBox;
            const behavior = smooth !== false ? 'smooth' : 'auto';
            if(force || messageBox.scrollHeight - messageBox.clientHeight <= messageBox.scrollTop + 100) {
                this.$nextTick(() => {
                    messageBox.scrollTo({
                        top: messageBox.scrollHeight,
                        behavior: behavior
                    });
                })
            }
        },
        scrollToTop(smooth) {
            const behavior = smooth !== false ? 'smooth' : 'auto';
            this.$refs.messageBox.scrollTo({
                top: 0,
                behavior: behavior
            });
        },
        genId() {
            return new Date().getTime() + String(Math.floor(Math.random() * 1000));
        },
        formatDate(timestamp) {
            if (!timestamp) return '';
            const date = new Date(timestamp);
            const now = new Date();
            // 如果是当天的，返回 小时:分钟
            if (date.toDateString() === now.toDateString()) {
                const hours = date.getHours().toString().padStart(2, '0');
                const minutes = date.getMinutes().toString().padStart(2, '0');
                return `${hours}:${minutes}`;
            }
            // 如果是昨天的，返回昨天
            const yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1);
            if (date.toDateString() === yesterday.toDateString()) {
                return '昨天';
            }
            // 如果是前天的，返回前天
            const beforeYesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 2);
            if (date.toDateString() === beforeYesterday.toDateString()) {
                return '前天';
            }
            // 如果是今年的，返回 月-日 如 01-25
            if (date.getFullYear() === now.getFullYear()) {
                const month = (date.getMonth() + 1).toString().padStart(2, '0');
                const day = date.getDate().toString().padStart(2, '0');
                return `${month}-${day}`;
            }
            // 其它返回 年-月-日 如 2021-03-16
            const year = date.getFullYear().toString();
            const month = (date.getMonth() + 1).toString().padStart(2, '0');
            const day = date.getDate().toString().padStart(2, '0');
            return `${year}-${month}-${day}`;
        },
        checkMobile() {
            this.isMobile = window.innerWidth < 768; // 假设小于768px的宽度为移动端
        },
        handleInputFocus() {
            // 修复苹果浏览器键盘遮挡输入框问题
            setTimeout(()=>{
                if (!this.innerHeight) {
                    this.innerHeight = window.innerHeight < 500 ? window.innerHeight : 395;
                } else {
                    this.innerHeight = window.innerHeight < 500 ? window.innerHeight : this.innerHeight;
                }
                window.scrollTo(0,document.body.scrollHeight - this.innerHeight);
            }, 100);
        },
        slideOut() {
            setTimeout(()=>{this.showAddressBook = true;}, 80);
            this.isSlidedOut = true;
            this.isSlidedIn = false;
        }
    }
}).mount('#app');